<!DOCTYPE HTML>
<html>
<head>
<title>tiling</title>
<!--

-->
<meta http-equiv="X-UA-Compatible" content="chrome=1">
<meta content="width=device-width; initial-scale=1.0;  maximum-scale=1.0; user-scalable=0;" name="viewport" />
<meta name="apple-mobile-web-app-capable" content="yes" />
<meta name="apple-mobile-web-app-status-bar-style" content="black" />
<style>
	body { padding:0px; margin:0px; color: white; background-color: black; }
	input { 
		font-size:16px;
		padding: 0px;
	}
	* {font-family: sans-serif}
	form { padding:0px; margin:0px; }
</style>
<script src="../../lib/canvas.js"></script>
<script>
	var phi;
	var v = new Object();
	var bars = new Array();
	var ints = new Array();
	var verts = new Array();
	var tiles = new Array();
	var tiles_ints = new Array();
	var edges = new Array();

	var animation = new Object();
	var task = new Object();

	var long_d;
	var opts = new Object();

	function on_resize() {
		canvas.width = window.innerWidth;
		canvas.height = window.innerHeight - 60;
		ctx.globalCompositeOperation = 'lighter';
		//console_log("version 2009-10-12 00:22");

		set_viewport(0, 0, 60);
	}

	function init() {
		canvas_init();
		phi = Math.sqrt(5) + 1;
		phi /= 2;

		window.onresize = on_resize;
		on_resize();
	}
	
	function draw_decagon(r, x, y, theta) {
		return draw_polygon(
			get_regular_polygon_points(10, x, y, r, theta)
		);
	}

	function get_midpoints(p) {
		var i;
		var o = new Array();
		if (p.length < 2) { return o; }
		var pp = p[0];
		for (i = 1; i < p.length; ++i) {
			o.push(get_midpoint(pp, p[i]));
			pp = p[i];
		}
		o.push(get_midpoint(pp, p[0]));
		return o;
	}

	function get_midpoint(p1, p2) {
		var o = new Object();
		o.x = (p1.x + p2.x) / 2;
		o.y = (p1.y + p2.y) / 2;
		return o;
	}

	function get_midpoint_r(r, p1, p2) {
		var dx = (p2.x - p1.x);
		var dy = (p2.y - p1.y);
		var d = Math.sqrt(dx * dx + dy * dy);
		var th = Math.atan2(dy, dx);
		var o = new Object();
		o.x = p2.x + r * d * Math.cos(th);
		o.y = p2.y + r * d * Math.sin(th);
		return o;
	}

	function get_intersection(a, b) {
		//log_bar("a", a);
		//log_bar("b", b);
		var p1 = a[0];
		var p2 = a[1];
		var p3 = b[0];
		var p4 = b[1];

		// p1 and p2 are on one line,
		// p3 and p4 are on the other
		
		var d = (
			(p4.y - p3.y) * (p2.x - p1.x) -
			(p4.x - p3.x) * (p2.y - p1.y)
		);
		if (0 == d) { return null; }

		var u = (
			(p4.x - p3.x) * (p1.y - p3.y) -
			(p4.y - p3.y) * (p1.x - p3.x)
		) / d;

		var o = new Object();
		o.x = p1.x + u * (p2.x - p1.x);
		o.y = p1.y + u * (p2.y - p1.y);

		//log_bar("intersects", [o]);
		return o;
	}

	function get_distance(p1, p2) {
		var dx = (p2.x - p1.x);
		var dy = (p2.y - p1.y);
		return Math.sqrt(dx * dx + dy * dy);
	}

	function get_regular_polygon_points(sides, x, y, r, theta) {
		var p = new Array();
		var th;
		var i;
		for (i = 0; i < sides; ++i) {
			th = theta + i * 2 * Math.PI / sides;
			p[i] = new Object();
			p[i].x = x + r * Math.cos(th);
			p[i].y = y - r * Math.sin(th);
		}
		return p;
	}

	function draw_polygon(p) {
		ctx.save();
		ctx.beginPath();
		ctx.moveTo(p[0].x, p[0].y);
		for (i = 1; i < p.length; ++i) {
			ctx.lineTo(p[i].x, p[i].y);
		}
		ctx.closePath();
		ctx.stroke();
		ctx.restore();
		return p;
	}

	function log_bar(label, b) {
		var o = '';
		o += label;
		for (var i = 0; i < b.length; ++i) {
			o += " (" + b[i].x + "," + b[i].y + ")";
		}
		console_log(o);
	}

	function draw_segments(b) {
		//log_bar("draw", b);
		ctx.save();
		ctx.beginPath();
		ctx.moveTo(b[0].x, b[0].y);
		for (var i = 1; i < b.length; ++i) {
			ctx.lineTo(b[i].x, b[i].y);
			//log_bar("draw", b);
		}
		ctx.stroke();
		ctx.restore();
	}

	function draw_bar(s) {
		var b = find_view_bar(s);
		if (null == b) {return; }
		b = scale_points(b);
		draw_segments(b);
	}

	function find_view_bar(s) {
		var o = new Array();
		o.push(get_intersection(s, v.l));
		o.push(get_intersection(s, v.t));
		o.push(get_intersection(s, v.r));
		o.push(get_intersection(s, v.b));

		// remove all points that are outside the viewport

		var b = new Array();
		var p;
		while (o.length > 0) {
			p = o.shift();
			if (null == p) { continue; }
			if (p.x < v.l[0].x - .00001) { continue; }
			if (p.x > v.r[0].x + .00001) { continue; }
			if (p.y < v.t[0].y - .00001) { continue; }
			if (p.y > v.b[0].y + .00001) { continue; }
			b.push(p);
			if (2 == b.length) {break; }
		}
		//log_bar("scaled", b);
		if (2 == b.length) {return b;}
		return null;
	}

	function scale_points(p) {
		var o = new Array();
		for (var i in p) {
			o[i] = {
				x: p[i].x * v.z + v.cx - v.x * v.z, 
				y: p[i].y * v.z + v.cy - v.y * v.z
			}
		}
		return o;
	}

	function find_parallel_line(p, r) {

		var dx = (p[0].x - p[1].x);
		var dy = (p[0].y - p[1].y);

		var th = Math.atan2(dy, dx) - Math.PI / 2;

		var o = new Array();
		for (var i  in p) {
			o[i] = {x: p[i].x, y: p[i].y};
			o[i].x += r * Math.cos(th);
			o[i].y += r * Math.sin(th);
		}

		return o;
	}

	function find_center_bar(theta) {
		return find_bar(0, 0, theta);
	}

	function find_bar(x, y, theta) {
		return [{x: x, y:y}, {x: x + Math.cos(theta), y: y - Math.sin(theta)}]
	}

	function draw_center() {
		ctx.strokeStyle = "white";
		var theta;
		for (var i = 0; i < 5; ++i) {
			theta = (5 - i * 2) * Math.PI / 5;
			var c = find_center_bar(theta);
			var o = find_bar(v.x, v.y, theta + Math.PI / 2);
			var b = [{x:0, y:0}, get_intersection(c, o)];
			//ctx.strokeStyle = rainbow_color(i / 5);
			draw_bar(c);
			draw_bar(o);
			ctx.strokeStyle = "white";
			draw_segments(scale_points(b));
		}
	}

	function find_intersection(a, b) {
		var i = [a.k, b.k];
		i.sort();
		var k = i.join(':');
		if (null == ints[k]) { 
			ints[k] = get_intersection(a.bar, b.bar);
			// why are we trying to find intersecting parallell bars?
			if (null == ints[k]) {
				delete ints[k];
				return null;
			}
			ints[k].bars = [a, b];
			//ints[k].cross = find_cross_bars(a, b);
			ints[k].t = 'int';
			ints[k].k = k;
			ints[k].links = [];
			ints[k].links[a.k] = new Object();
			ints[k].links[b.k] = new Object();
			tiles_ints[k] = [];
			ints[k].tiles = tiles_ints[k];
		}
		return {p: ints[k], k: k, tiles: tiles_ints[k]};
	}

	function find_all_intersections() {

		for (var i in bars) {
			var a = new Array();
			var bar = bars[i];
			for (var j in bars) {
				if (bars[i].g == bars[j].g) { continue; }
				var inter = find_intersection(bars[i], bars[j]);
				inter.bars = [bars[i], bars[j]];
				a.push(inter);
			}
			a.sort(sort_intersections);
			for (var j = 1; j < a.length - 1; ++j) {
				var p = a[j - 1].p;
				var c = a[j].p;
				var n = a[j + 1].p;
				var k = bar.k;
				p.links[k].next = c;
				c.links[k].prev = p;
				c.links[k].next = n;
				n.links[k].prev = c;
			}
			bars[i].ints = a;
		}
	}

	function sort_intersections(a, b) {
		var o = a.p.x - b.p.x;
		if (0 == o) {return a.p.y - b.p.y;}
		return o;
	}

	function draw_tile_edges(t) {
		for (var i in t.edges) { 
			if (null == animation.edges[i]) {
				draw_edge(t.edges[i]); 
				animation.edges[i] = true;
			}
		}
	}

	function draw_tile_tip(t) {
		if (null == t.tip) { return; }
		var dot = new_draw_dot();
		dot.clr = "green";
		dot.r = 3;
		var p;
		p = scale_points([t.tip])[0];
		dot.x = p.x;
		dot.y = p.y;
		//draw_dot(dot);
		ctx.strokeStyle = "gray";
		draw_segments(scale_points([t.root, t.tip]));
	}

	function draw_tile_bars(t) {

		if (!opts.bars) { return; }

		var a = new Array();
		for (var i in t.bars) {
			if (null == animation.bars[t.bars[i].k]) {
				a.push(t.bars[i]);
				animation.bars[t.bars[i].k] = true;
			}
		}
		if (0 < a.length) {
			draw_bars(a);
		}
	}

	function draw_tile_verts(t) {
		for (var i in t.verts) {
			var k = t.verts[i].k;
			if (null == animation.verts[k]) {
				draw_vert(t.verts[i]); 
				animation.verts[k] = true;
			}
		}
	}

	function draw_tile(t) {
		draw_tile_bars(t);
		draw_tile_verts(t);
		//draw_tile_tip(t);
		draw_tile_edges(t);
		draw_tile_arcs(t);
	}

	function old_draw_tile(t) {

		if (null == t.tip) { return; }

		var a = [t.root];
		for (var i in t.wings) {
			a.push(t.wings[i]);
			a.push(t.tip);
		}
		a.pop();
		a = scale_points(a);

		var clr = [0, 0, 0];
		var iclr;
		var bright = ((opts.bars || opts.ints)? 32 : 4); 
		for (var i in t.bars) {
			iclr = rainbow_numbers(.5 + t.bars[i].d / 12);
			for (var j in iclr) { 
				clr[j] += Math.floor(iclr[j] * 1/bright); 
				if (clr[j] > 255) { clr[j] = 255; }
			}
		}
		//if (opts.bars) {
			ctx.fillStyle = 'rgb(' + clr.join(',') + ')';
		//} else {
		//	ctx.fillStyle = 'rgb(8, 8, 8)';
		//}
		if (opts.tile) {
			ctx.save();
			ctx.beginPath();
			ctx.moveTo(a[0].x, a[0].y);
			for (var i = 1; i < a.length; ++i) {
				ctx.lineTo(a[i].x, a[i].y);
			}
			ctx.closePath();
			ctx.fill();
			ctx.restore();
		}

		for (var i in t.edges) { 
			if (null == animation.edges[i]) {
				draw_edge(t.edges[i]); 
				animation.edges[i] = true;
			}
		}

		for (var i in t.verts) {
			if (null == animation.verts[i]) {
				draw_vert(t.verts[i]); 
				animation.verts[i] = true;
			}
		}

		if (opts.ints) {
			for (var i in t.ints) {
				if (null == animation.ints[t.ints[i].k]) {
					draw_intersection(t.ints[i].p); 
					animation.ints[t.ints[i].k] = true;
				}
			}
		}

		if (opts.bars) {
			var a = new Array();
			for (var i in t.bars) {
				if (null == animation.bars[t.bars[i].k]) {
					a.push(t.bars[i]);
					animation.bars[t.bars[i].k] = true;
				}
			}
			if (0 < a.length) {
				draw_bars(a);
			}
		}
	}

	function draw_tile_arcs(t) {
		if (null == t.tip) { return; }
		if (null == t.root) { return; }
		var d;


		th = Math.PI - t.theta;
		var m;
		d = long_d / phi;
		if ('kite' == t.t) {
			m = 2;
			d = d / phi;
		} else {
			m = 3;
			d = d * (1 - 1/phi);
		}

		p = scale_points([t.tip])[0];

		if (opts.arc_b) {
			ctx.strokeStyle = "gray";
			ctx.save();
			ctx.beginPath();
			ctx.arc(p.x, p.y, d * v.z, th - m * Math.PI / 5, th + m * Math.PI / 5, false);
			ctx.stroke();
			ctx.restore();
		}


		if ('kite' == t.t) {
			d = long_d / phi;
		} else {
			d = long_d * (1 - 1/phi);
		}


		th = - t.theta;

		var p = scale_points([t.root])[0];

		if (opts.arc_a) {
			ctx.strokeStyle = "white";
			ctx.save();
			ctx.beginPath();
			ctx.arc(p.x, p.y, d * v.z, th - Math.PI / 5, th + Math.PI / 5, false);
			ctx.stroke();
			ctx.restore();
		}


	}

	function animate_tiles() {
		if (! task.ready) { return; }
		find_options();
		var a = animation;
		if (null != a.interval) { clearInterval(a.interval); }
		canvas_clear();
		draw_frame();
		//if (opts.bars) { draw_all_bars(); }
		a.tiles = new Array();
		a.edges = new Array();
		a.verts = new Array();
		a.ints = new Array();
		a.bars = new Array();
		var o = new Array();
		draw_all_intersections();
		for (var i in tiles) { o.push(tiles[i]); }
		for (var i in o) {
			a.tiles.unshift(o[i]);
			var n = Math.floor(Math.random() * a.tiles.length);
			var t = a.tiles[0]; 
			a.tiles[0] = a.tiles[n]; 
			a.tiles[n] = t;
		}
		var tn = 128;
		if (opts.ints) { tn = 16; }
		a.interval = setInterval("draw_next_tiles(" + tn +")", 10);
	}

	function draw_frame() {
		var a = scale_points([
			{x: v.d.l, y: v.d.t},
			{x: v.d.r, y: v.d.t},
			{x: v.d.r, y: v.d.b},
			{x: v.d.l, y: v.d.b}

		]);
		ctx.save();
		ctx.beginPath();
		var p = a.shift();
		ctx.moveTo(p.x, p.y);
		while (a.length > 0) { 
			p = a.shift(); 
			ctx.lineTo(p.x, p.y); 
		}
		ctx.closePath();
		ctx.strokeStyle = "gray";
		ctx.lineWidth = 5;
		ctx.stroke();
		ctx.lineWidth = 1;
		ctx.restore();
	}

	function draw_next_tiles(n) {
		var a = animation;
		//status_update("draw tiles" + n + ":" + a.tiles.length);
		for (var i = 0; i < n; ++i) {
			var t = a.tiles.shift();
			if (null == t) { 
				clearInterval(a.interval);
				//draw_all_bars(2.0);
				return; 
			};
			draw_tile(t);
			//draw_arcs(t);
		}
	}

	function count_tiles(o) { return count_things('tiles', o);}
	function count_roots(o) { return count_things('roots', o);}

	function count_things(k, o) {
		if (null == o) { return 0; }
		if (null == o[k]) { return 0; }
		var n = 0;
		for (var i in o[k]) { ++n; }
		return n;
	}

	function find_common_tiles(a) {
		// find the tiles common to items in a
		var o = new Array();
		var n = 0; // number of items in a.
		for (var i in a) {
			++n;
			for (var t in a[i].tiles) {
				var k = a[i].tiles[t].k;
				if (null == o[k]) { o[k] = 0; }
				++o[k];
			}
		}
		var c = new Array();
		for (var k in o) {
			if (n == o[k]) { c.push(tiles[k]); }
		}
		return c;
	}

	function find_common_bars(a) {
		var o = new Array();
		var b = new Array();
		var len = 0;
		for (var i in a) {
			++len;
			for (var j in a[i].bars) {
				var k = a[i].bars[j].k;
				if (null == b[k]) {b[k] = 0;}
				b[k]++;
			}
		}
		for (var k in b) {
			if (len == b[k]) { o.push(bars[k]); }
		}
		return o;
	}

	function find_cross_bars(a, b) {
		var i = find_intersection(a, b);
		var th = (a.theta + b.theta) / 2;
		return [
			find_bar(i.p.x, i.p.y, th),
			find_bar(i.p.x, i.p.y, th + Math.PI / 2)
		];
	}

	function find_cross_bar(a, b) {
		var i = find_intersection(a, b);
		var th = Math.PI / 2 + (a.theta + b.theta) / 2;
		return find_bar(i.p.x, i.p.y, th);
	}

	function find_vertex(p) {
		var kx = Math.floor(p.x * 64);
		var ky = Math.floor(p.y * 64);
		var a = [];
		for (var x = kx; x < kx + 4; ++x) {
			for (var y = ky; y < ky + 4; ++y) {
				a.push('<' + x + ':' + y + '>');
			}
		}
		a.reverse();
		var k;
		for (var i = 0; i < a.length; ++i) {
			k = a[i];
			if (null != verts[k]) { break; }
		}

		if (null == verts[k]) {
			verts[k] = {
				x: p.x,
				y: p.y,
				tiles: [],
				edges: [],
				k: k
			};
		}
		return verts[k];
	}

	function find_root_vertex(t) {
		// do no harm
		if (null != t.root) { return t.root; }
		//the "root" vertex is where long edges meet
		// first try to find a neighbor's root vertex
		for (var i in t.points) {
			for (var j in t.points[i].tiles) {
				var ot = t.points[i].tiles[j];
				if (ot.t == t.t) {
					if (null != ot.root) {
						t.root = ot.root;
						t.root.tiles[t.k] = t;
						t.root.roots[t.k] = t;
						return ot.root;
					}
				}
			}
		}

		var a = find_cross_bar(t.bars[0], t.bars[1]);
		var b = find_cross_bar(t.bars[0], t.bars[2]);
		var o = find_vertex(get_intersection(a, b));
		o.t = (t.t == 'kite');
		o.tiles = [t];
		o.roots = [t];
		o.tips = [];
		t.root = o;
		return o;
	}

	function calculate_long_edge(e) {
		//the edge is a line segment crossing
		//the intersection the tiles share

		var b = find_common_bars(e.tiles);
		var i = find_intersection(b[0], b[1]);
		var t = new Array();
		var c = new Array();
		for (var k in e.tiles) { t.push(e.tiles[k]); }
		var l; 
		if (t[0].t != t[1].t) {
			l = [find_vertex(t[0].root), find_vertex(t[1].root)];
			if (null == long_d) {
				long_d = get_distance(l[0], l[1]);
			}
			e.fix = false;
		} else {
			l = [t[0].root, i.p]; 
			e.fix = t[0].t;
		}
		e.points = l;
		if (null != long_d) {fix_long_edge(e);}
		//ctx.strokeStyle = "rgb(1,1,1)";
		//ctx.lineWidth = 1;
		//draw_bar(e.points);

	}

	function fix_long_edge(e) {
		var r = e.points[0];
		if (e.fix) {
			var theta = get_theta(e.points);
			e.points = [find_vertex({x: r.x, y: r.y}), 
			find_vertex({x: r.x + long_d * Math.cos(theta), 
			y: r.y - long_d * Math.sin(theta)})]
			e.points[1].t = (e.fix == 'dart');
			e.fix = false;
		}
	}

	function get_theta(b) {
		var dx = b[1].x - b[0].x;
		var dy = b[1].y - b[0].y;
		return Math.atan2(-dy, dx);
	}

	function find_short_edge(a) {
		var l = new Array();
		for (var k in a) { l.push(a[k].k) }
		if (l.length != 2) { return null;}
		l.sort();
		k = '(' + l.join('+') + ')'; 
		if (null == edges[k]) {
			edges[k] = {
				k: k,
				t: 'short',
				points: a,
				tiles: []
			}
		}
		return edges[k];

	}

	function find_tile(t, a, b) {
		var i = [a.bars[0].k, a.bars[1].k, b.bars[1].k];
		i.sort();

		var k = '{' + i.join('*') + '}';

		if (null == tiles[k]) {
			tiles[k] = { t: t, points: [a, b], k: k };
			tiles[k].points[0].tiles[k] = tiles[k];
			tiles[k].points[1].tiles[k] = tiles[k];
			tiles[k].bars = [a.bars[0], a.bars[1], b.bars[1]];
			tiles[k].ints = new Array();
			tiles[k].ints.push(find_intersection( tiles[k].bars[0], tiles[k].bars[1]));
			tiles[k].ints.push(find_intersection( tiles[k].bars[0], tiles[k].bars[2]));
			tiles[k].ints.push(find_intersection( tiles[k].bars[1], tiles[k].bars[2]));
			tiles[k].edges = new Array();
			tiles[k].verts = new Array();
			//tiles[k].root = find_root_vertex(tiles[k]);
			tiles[k].wings = new Array();
			tiles[k].tip = null;
			tiles[k].root = null;
			for (var i in tiles[k].bars) {
				tiles[k].bars[i].tiles[k] = tiles[k];
			}
			for (var i in tiles[k].ints) {
				ints[tiles[k].ints[i].k].tiles[k] = tiles[k];
			}
		}

		return tiles[k];
	}

	function find_kites(bar) {
		var b = bar.ints;
		var o = new Array();
		if (b.length < 2) {return o;}
		var p = b[0];
		for (var i = 1; i < b.length; ++i) {
			var d = get_distance(p.p, b[i].p);
			if (d < .25) { 
				find_tile('kite', p, b[i]); 
			}
			p = b[i];
		}
	}

	function find_edge(a, b) {
		var ak = [a.k, b.k];
		ak.sort;
		var k = '[' + ak.join('|') + ']';
		if (null == edges[k]) {
			edges[k] = {
				k: k,
				verts: [a, b],
				tiles: []
			};
		}

		var vert;
		for (var i in edges[k].verts) {
			vert = edges[k].verts[i];
			vert.edges[k] = edges[k];
		}

		return edges[k];
	}

	function draw_edge(e) {
		var b = e.verts;
		if (opts.tile || opts.arc_a || opts.arc_b) {
			ctx.strokeStyle = "rgb(31,31,31)";
		} else {
			ctx.strokeStyle = "gray";
		}
		if ('short' == e.t && ! opts.b_short) { return; }
		if ('long' == e.t && ! opts.b_long) { return; }
		draw_segments(scale_points(b));
	}

	function draw_all_edges() {
		for (var i in edges) { draw_edge(edges[i]); }
	}

	function draw_vert(c) {
		var dot = new_draw_dot();
		dot.clr = "white";
		dot.r = 2;
		var p;

		if (c.t) {
			dot.clr = "white";
			dot.r = phi;
			if (! opts.vert_a) { return; }
		} else {
			dot.clr = "gray";
			dot.r = 1;
			if (! opts.vert_b) { return; }
		}
		p = scale_points([c])[0];
		dot.x = p.x;
		dot.y = p.y;
		draw_dot(dot);
	}

	function draw_all_verts() {
		var dot = new_draw_dot();
		dot.clr = "white";
		dot.r = 2;
		var p;
		for (var i in verts) { draw_vert(verts[i]); }
	}

	function draw_intersection(t) {
		var dot = new_draw_dot();
		dot.clr = "gray";
		dot.r = 2;
		var p;
		p = scale_points([t])[0];
		dot.x = p.x;
		dot.y = p.y;
		//draw_dot(dot);
		ctx.strokeStyle = "rgb(1, 1, 1)";
		for (var i in t.cross) { draw_bar(t.cross[i]); }
	}

	function draw_all_intersections() {
		var dot = new_draw_dot();
		dot.clr = "white";
		dot.r = 2;
		var p;
		for (var i in ints) {
			p = scale_points([ints[i]])[0];
			dot.x = p.x;
			dot.y = p.y;
			draw_dot(dot);
			ctx.strokeStyle = "rgb(3, 3, 3)";
			/*
			for (var j in ints[i].cross) { 
				draw_bar(ints[i].cross[j]); 
			}
			*/
		}
	}

	function draw_all_bars(n) {
		draw_bars(bars, n);
	}

	function draw_bars(b, n) {
		for (var k in b) {
			var bar = b[k];
			var clr = rainbow_color(.5 + bar.d / 12);
			ctx.strokeStyle = clr;
			ctx.lineWidth = 1;
			if (null != n) {ctx.lineWidth = n};
			draw_bar(bar.bar);
			ctx.lineWidth = 1.0;
		}
	}

	function clear_globals() {
		bars = new Array();
		ints = new Array();
		tiles_ints = new Array();
		tiles = new Array();
		edges = new Array();
		verts = new Array();
	}

	function find_all_bars() {
		var a = new Array();
		var d = 1;
		for (var i = 0; i < 5; ++i) {
			theta = (1 + i * 2) * Math.PI / 5;
			a.push(find_bars(theta, d, i));
			d = -d;
		}
		return a;
	}

	function sort_bars(a, b) {
		return a.i - b.i;
	}

	function find_bars(theta, r, ind) {
		// find the ammann bars for the given direction
		// that fit in the viewport
		var c = find_center_bar(theta);
		var e = find_center_bar(theta + Math.PI / 2);
		var o = find_bar(v.x, v.y, theta);
		var b = [{x:0, y:0}, get_intersection(e, o)];
		var d = get_distance(b[0], b[1]);
		// we want the approximate musical sequence term
		// given distance d

		// the formula is:
		// bars = a + b
		// a ~= d / (2 + phi);
		// b ~= a * phi
		// bars = 
		var ta = Math.abs(d / (2 + phi));
		var tb = Math.abs(ta * phi);
		var t = Math.round(ta + tb);


		//t = 0;
		//a = [o, e, c];
		a = [];
		var ti = t;
		var bar;
		var tried = new Array();

		var tries = [
			[t, 1],
			[t, -1],
			[-t, 1],
			[-t, -1]
		];

		for (var i = 0; i < tries.length; ++i) {
		ti = tries[i][0];
		var incr = tries[i][1];
		var md;
		while(true) { 
		if (!tried[ti]) {
			tried[ti] = true;
			md = r * find_musical_distance(ti);
			bar = find_parallel_line(c, md);
			if (null == find_view_bar(bar)) { break; }
			a.push({
			bar: bar, 
			d: md, 
			i: ti, 
			theta: theta,
			g: ind,
			tiles: [],
			k: '[' + ind + ":" + ti + ']'
			});
		} 
		ti += incr; }
		}
		a.sort(sort_bars);

		for (i = 0; i < a.length; ++i) {
			bars[a[i].k] = a[i];
		}

		return a;

	}

	function clear_tasks() {
		task.ready = false;
		if (null != animation.interval) { clearInterval(animation.interval); }
		if (null != task.interval) { clearTimeout(task.interval); }
	}

	function task_find_bars() {
		clearTimeout(task.interval);

		find_all_bars();
		draw_all_bars(0.2);
		task.interval = setTimeout('task_find_kites()', 100);
	}

	function task_find_next_kite_roots() {
		clearTimeout(task.interval);
		var a = new Array();
		for (var i = 0; i < 64; ++i) {
			if (task.tiles.length < 1) { break; }
			a.push(task.tiles.shift());
		}
		for (var i in a) { 
			find_root_vertex(a[i]); 
			if (null != a[i].root) { 
				a[i].verts = [a[i].root]; 
			}
		}
	       	status_update("roots: " + Math.floor(100 * (task.total - task.tiles.length) / task.total) + '%');
		if (a.length > 0) {
		var a = new Array();
			task.interval = setTimeout('task_find_next_kite_roots()', 10);
		} else {
			task.interval = setTimeout('task_find_kite_root_tips()', 10);

		}
	}

	function task_ready() {
		clearTimeout(task.interval);
		task.ready = true;
		animate_tiles();
	}

	function find_uncommon_bars(a) {
		var o = new Array();
		var n = 0;
		for (var i in a) {
			++n;
			for (var j in a[i].bars) {
				var b = a[i].bars[j]
				if (null == o[b.k]) { o[b.k] = 0; }
				++o[b.k];
			}
		}
		var c = new Array();
		for (var k in o) {
			if (o[k] < n) { c.push(bars[k]); }
		}
		return c;
	}

	function find_kite_root_tip(t) { 
		// if our root has two kites
		// then the bar we don't share with our twin kite
		// and the bar it doesn't share with us
		// both might belong to another kite that has our root
		// as its tip (complicated)

		// one more thing... the tile we find this way must
		// not have the same root as us.

		if (null == t.root) { return; } // should never happen
		if (task.tried[t.root.k]) {return; }
		task.tried[t.root.k] = true;

		var a = find_common_tiles(find_uncommon_bars(t.root.tiles));
		var ot = null;

		for (var i in a) {
			if (a[i].root.k == t.root.k) { continue; }
			ot = a[i];
			break;
		}

		if (null == ot) { return; }
		//console_log("neighbors:" + t.k + " -> " + ot.k);

		ot.tip = t.root;
		ot.theta = get_theta([ot.root, ot.tip]);
		t.root.tips[ot.k] = ot;
		t.root.tiles[ot.k] = ot;

		if (null == long_d) {
			long_d = get_distance(ot.root, ot.tip);
		}

	}

	function find_kite_tip(t) { 
		if (null != t.tip) { return; }
		// this tip is shared by two kites
		// there is another kite with a bar in common with us
		// and our other bars cross nearby

		// one bar is ours.
		// how do we find the other one

		// the other bar is the next intersection out
		// so there are our two non-root bars, and the bar
		// that intersects them both next.
		// we are looking for a kite that has two of them

		// to write this we need a way to find the next or
		// previous intersection.  


		// find the bars

		var r = t.bars[0];
		var a = t.bars[1];
		var b = t.bars[2];

		// now find their intersections
		var ri = find_intersection(a, b).p;

		// now find the next intersection
		var ni = ri.links[a.k].next;
		if (ni == null || ni.k == ri.k) {
			ni = ri.links[a.k].prev;
		}

		var c = null;
		if (null != ni) {
			for (var k in ni.links) {
				if (k == a.k) { continue; }
				if (k == b.k) { continue; }
				if (k == r.k) { continue; }
				c = bars[k];
			}
		}

		var ot = null;
		if (c != null) {
			// we want the tile with bars a and c or b and c

			var ta = [];
			var ti;
			ti = find_intersection(a, c);
			if (null != ti) {
				ti = find_common_tiles([ti.p]);
				for (var i in ti) { ta[ti[i].k] = ti[i]; }
			}
			ti = find_intersection(b, c);
			if (null != ti) {
				ti = find_common_tiles([ti.p]);
				for (var i in ti) { ta[ti[i].k] = ti[i]; }
			}
			delete ta[t.k];

			for (var i in ta) {
				if (t.k == ta[i].k) { continue; }
				if (null != ta[i].tip) {
					if (
						ta[i].tip.k == t.root.k ||
						ta[i].root.k == t.root.k
					) {
						continue;
					}
				}
				ot = ta[i]; break;
			}
		}

		if (null != ot) {
			if (null != ot.tip) {
				t.tip = ot.tip;
				t.tip.tips[t.k] = t;
				t.tip.tiles[t.k] = t;
				t.verts = [t.root, t.tip];
				t.theta = get_theta([t.root, t.tip]);
				return;
			}
		}

		// create a new tip vertex
		var m = get_midpoint(t.points[0].p, t.points[1].p);
		var theta = get_theta([t.root, m]);
		t.theta = theta;

		t.tip = find_vertex({x: t.root.x + long_d * Math.cos(theta), y: t.root.y - long_d * Math.sin(theta)});
		t.tip.t = true;
		t.tip.tips = [];
		t.tip.tiles = [];
		t.tip.tips[t.k] = t;
		t.tip.tiles[t.k] = t;
		t.verts = [t.root, t.tip];

		if (null != ot) {
			ot.tip = t.tip;
			t.tip.tips[ot.k] = ot;
			t.tip.tiles[ot.k] = ot;
			ot.verts = [ot.root, ot.tip];
			ot.theta = get_theta([ot.root, ot.tip]);
		}

	}

	function find_kite_wings(t) { 
		if (4 == t.verts.length) { return; }
		if (null == t.root) { return; } // should never happen
		if (null == t.tip) { return; } // should never happen
		var want = [];

		for (var i in t.points) {
			var k = t.points[i].k;
			if (null == t.wings[k]) {
				want[k] = ints[k];
			}
		}

		for (var i in t.root.tiles) {
			for (var k in want) {
				var w = t.root.tiles[i].wings[k];
				if (null != w) {
					delete want[k];
					t.wings[k] = w;
				} else {
					// did we create this by asking for it?
					// just in case...
				       	delete t.root.tiles[i].wings[k];
				}
			}
		}

		var r = t.root;
		for (var k in want) {
			var theta = get_theta([t.root, ints[k]]);
			t.wings[k] =
			find_vertex({x: t.root.x + long_d * Math.cos(theta), 
			y: t.root.y - long_d * Math.sin(theta)});
		}

		t.verts = [t.root];
		var e;
		for (var k in t.wings) {
			t.verts.push(t.wings[k]);
			t.verts.push(t.tip);
			e = find_edge(t.root, t.wings[k]);
			e.t = 'long'; e.tiles[t.k] = t; t.edges[e.k] = e;
			e = find_edge(t.tip, t.wings[k]);
			e.t = 'short'; e.tiles[t.k] = t; t.edges[e.k] = e;
		}
		t.verts.pop();

	}

	function find_next_vertex(vert, e) {
		for (var i in e.verts) {
			if (vert.k == e.verts[i].k) { continue; }
			return e.verts[i];
		}
		//console_log("no next vert! " + e.k + " " + vert.k);
	}

	function find_next_edge(prev, next, e) {
		var o = [];
		o.vert = find_next_vertex(next, e);

		var best;
		var vert;
		var d;
		var edge;
		for (var i in o.vert.edges) {
			edge = o.vert.edges[i];
			vert = null;
			d = null;
			if (edge.k == e.k) { continue; }
			if (1 == count_tiles(edge)) {
				vert = find_next_vertex(o.vert, edge);
				d = get_distance(prev, vert);
				if (null == best || d < best) {
					o.edge = edge;
					best = d;
				}
			}
		}

		return o;
	}

	function find_empty_patch(e) {
		if (task.tried[e.k]) { return []; }
		var a = [];

		// to find an empty patch, 
		// proceed along edge to the next vertex
		// if we've encountered this vertex before, we're done

		// find all empty edges we haven't followed yet in the
		// next vertex.
		// if there is more than one empty edge, choose the one
		// with the vertex closest to the previous vertex

		// we can start with either vertex;
		var prev;
		for (var i in e.verts) {
			prev = e.verts[i];
			break;
		}
		var edge = e;
		var next = find_next_vertex(prev, edge);

		var o = [];
		while (true) {
			a.push(edge);
			o = find_next_edge(prev, next, edge);
			if (null == o.edge) { 
				//console_log(["no next edge!", prev, next, edge].join(" ! ")); 
				return []; 
			}
			if (null != task.tried[o.edge.k]) { return []; }
			prev = next;
			next = o.vert;
			edge = o.edge;
			if (edge.k == e.k) { break; }
			if (a.length > 10) { break; }
		}

		if (
			4 == a.length ||
			8 == a.length ||
			10 == a.length
		) {
			//console_log(e.k +" "+  a.length);
			ctx.strokeStyle = rainbow_color(Math.random());
			for (var i in a) { 
				task.tried[a[i].k] = true; 
				ctx.lineWidth = 3;
				draw_segments(scale_points(a[i].verts));
				ctx.lineWidth = 1;
			}
			return a;
		}
		//task.tried[e.k] = true;
			ctx.strokeStyle = "rgb(15,15,15)";
			ctx.lineWidth = 5;
			for (var i in a) {
				draw_segments(scale_points(a[i].verts));
			}
			ctx.lineWidth = 1;
		return [];
	}

	function find_darts(t) {
		var a = [];

		// if the edge is short
		// then find the other empty edge sharing the wing vertex

		// finding the root is harder
		// if there are two long edges at the end of each wing
		// and they meet at a point, this is the root

		for (var i in t.edges) {
			if (1 == count_tiles(t.edges[i])) {
				a.push(find_empty_patch(t.edges[i]));
			}
		}
	}

	function task_find_next_darts() {

		clearTimeout(task.interval);
		var a = new Array();
		for (var i = 0; i < 16; ++i) {
			if (task.tiles.length < 1) { break; }
			a.push(task.tiles.shift());
		}
		for (var i in a) { 
			find_darts(a[i]); 
		}
	       	status_update("darts: " + Math.floor(100 * (task.total - task.tiles.length) / task.total) + '%');
		if (a.length > 0) {
		var a = new Array();
			task.interval = setTimeout('task_find_next_darts()', 1000);
		} else {
			status_update("ready");
			task.interval = setTimeout('task_ready()', 5000);

		}
	}

	function task_find_darts() {
		clearTimeout(task.interval);
		task.tiles = new Array();
		task.tried = new Array();
		for (var i in tiles) {
			task.tiles.push(tiles[i]);
		}
		task.total = task.tiles.length;
		task.interval = setTimeout('task_find_next_darts()', 10);
	}

	function task_find_next_kite_wings() {

		clearTimeout(task.interval);
		var a = new Array();
		for (var i = 0; i < 64; ++i) {
			if (task.tiles.length < 1) { break; }
			a.push(task.tiles.shift());
		}
		for (var i in a) { 
			find_kite_wings(a[i]); 
		}
	       	status_update("wings: " + Math.floor(100 * (task.total - task.tiles.length) / task.total) + '%');
		if (a.length > 0) {
		var a = new Array();
			task.interval = setTimeout('task_find_next_kite_wings()', 10);
		} else {
			status_update("ready");
			task.interval = setTimeout('task_ready()', 10);

		}
	}

	function task_find_kite_wings() {
		clearTimeout(task.interval);
		task.tiles = new Array();
		task.tried = new Array();
		for (var i in tiles) {
			task.tiles.push(tiles[i]);
		}
		task.total = task.tiles.length;
		task.interval = setTimeout('task_find_next_kite_wings()', 10);
	}

	function task_find_next_kite_tips() {

		clearTimeout(task.interval);
		var a = new Array();
		for (var i = 0; i < 64; ++i) {
			if (task.tiles.length < 1) { break; }
			a.push(task.tiles.shift());
		}
		for (var i in a) { 
			find_kite_tip(a[i]); 
		}
	       	status_update("tips: " + Math.floor(100 * (task.total - task.tiles.length) / task.total) + '%');
		if (a.length > 0) {
		var a = new Array();
			task.interval = setTimeout('task_find_next_kite_tips()', 10);
		} else {
			task.interval = setTimeout('task_find_kite_wings()', 10);
		}
	}

	function task_find_kite_tips() {
		clearTimeout(task.interval);
		task.tiles = new Array();
		task.tried = new Array();
		for (var i in tiles) {
			task.tiles.push(tiles[i]);
		}
		task.total = task.tiles.length;
		task.interval = setTimeout('task_find_next_kite_tips()', 10);
	}

	function task_find_next_kite_root_tips() {

		clearTimeout(task.interval);
		var a = new Array();
		for (var i = 0; i < 64; ++i) {
			if (task.tiles.length < 1) { break; }
			a.push(task.tiles.shift());
		}
		for (var i in a) { 
			find_kite_root_tip(a[i]); 
		}
	       	status_update("root tips: " + Math.floor(100 * (task.total - task.tiles.length) / task.total) + '%');
		if (a.length > 0) {
		var a = new Array();
			task.interval = setTimeout('task_find_next_kite_root_tips()', 10);
		} else {
			task.interval = setTimeout('task_find_kite_tips()', 10);

		}
	}

	function task_find_kite_root_tips() {
		clearTimeout(task.interval);
		task.tiles = new Array();
		task.tried = new Array();
		for (var i in tiles) {
			task.tiles.push(tiles[i]);
		}
		task.total = task.tiles.length;
		task.interval = setTimeout('task_find_next_kite_root_tips()', 10);
	}

	function task_find_kite_roots() {
		clearTimeout(task.interval);
		task.tiles = new Array();
		for (var i in tiles) {
			task.tiles.push(tiles[i]);
		}
		task.total = task.tiles.length;
		task.interval = setTimeout('task_find_next_kite_roots()', 10);
	}

	function task_find_next_kites() {
		clearTimeout(task.interval);
		var b = task.bars.shift();
		if (null == b) {
			status_update("joining kites");
			task.interval = setTimeout('task_find_kite_roots()', 10);
			return;
		}
		find_kites(b);
		draw_bars([b], .5);
		task.interval = setTimeout('task_find_next_kites()', 10);
	}

	function task_find_kites() {
		clearTimeout(task.interval);
		find_all_intersections();

		task.bars = new Array();
		for (var i in bars) {
			task.bars.push(bars[i]);
		}

		task.interval = setTimeout('task_find_next_kites()', 10);
	}

	function task_finish_drawing() {
		clearTimeout(task.interval);

		task.ready = true;
		animate_tiles();
	}

	function redraw() {
		console_clear();
		clear_tasks();
		canvas_clear();
		clear_globals();
		ctx.strokeStyle = "white";

		task.interval = setTimeout('task_find_bars()', 10);


	}

	function view_scroll(x, y) {
		//how big is the screen?
		var hx = canvas.width / v.z;
		var hy = canvas.height / v.z;
		var scr = hx;
		if (hy < hx) { scr = hy; }
		scr /= phi;
		set_viewport(v.x + x * scr, v.y + y * scr, v.z);
	}

	function view_zoom(z) {
		set_viewport(v.x, v.y, v.z * Math.pow(phi, z));
	}

	function set_viewport(x, y, z) {
		status_clear();
		v.x = x;
		v.y = y;
		v.z = z;

		// what are the edges of the viewport ?
		// z is pixels/unit
		var w = phi + canvas.width / (phi * v.z);
		var h = phi + canvas.height / (phi * v.z);

		v.l = [{x: v.x - w, y: v.y - h}, {x: v.x - w, y: v.y + h}];
		v.t = [{x: v.x - w, y: v.y - h}, {x: v.x + w, y: v.y - h}];
		v.r = [{x: v.x + w, y: v.y - h}, {x: v.x + w, y: v.y + h}];
		v.b = [{x: v.x - w, y: v.y + h}, {x: v.x + w, y: v.y + h}];

		v.d = new Object;
		v.d.w = w - phi;
		v.d.h = h - phi;
		v.d.l = v.x - v.d.w;
		v.d.t = v.y - v.d.h;
		v.d.r = v.x + v.d.w;
		v.d.b = v.y + v.d.h;

		v.cx = canvas.width / 2;
		v.cy = canvas.height / 2;

		redraw();
	}

	function console_clear() {
		var c = document.getElementById("console");
		c.innerHTML = '';
	}

	function status_clear() {
		var c = document.getElementById("status");
		c.innerHTML = '';
	}

	function status_update(s) {
		var c = document.getElementById("status");
		var o = "" + s;
		o.replace(/&/, '&amp;');
		o.replace(/</, '&lt;');
		o.replace(/>/, '&gt;');
		o.replace(/'/, '&apos;');
		o.replace(/"/, '&quot;');
		c.innerHTML = o;
	}

	function console_log(s) {
		var c = document.getElementById("console");
		var o = "" + s;
		o.replace(/&/, '&amp;');
		o.replace(/</, '&lt;');
		o.replace(/>/, '&gt;');
		o.replace(/'/, '&apos;');
		o.replace(/"/, '&quot;');
		c.innerHTML += o + '<br />';
	}

	var middle_c;

	function find_musical_distance(t) {
		var c = (phi + 1) / 2;
		if (0 == t) { return 1 - c; }
		var m = ((t < 0) ? -1 : 1);
		var i = Math.abs(t) - 2;
		middle_c_term(i + 3);
		var d = c * m;
		if (i < 0) { return d; }
		d += m * (middle_c.S[i] + middle_c.L[i] * phi);
		return d;
	}

	function expand_middle_c() {
		// l, s l, l s l, s l l s l,
		if (0 == middle_c.m.length) {
			middle_c.m = ['L', 'S', 'L'];
			middle_c.L = [1, 1, 2];
			middle_c.S = [0, 1, 1];
			middle_c.a = 0;
			middle_c.b = 1;
			return;
		}
		var b = middle_c.m.length;
		var i;
		var t;
		for (i = middle_c.a; i < b; ++i) {
			t = middle_c.m[i];
			middle_c.m.push(t);
			middle_c.L.push(middle_c.L[middle_c.L.length - 1]);
			middle_c.S.push(middle_c.S[middle_c.S.length - 1]);
			++middle_c[t][middle_c[t].length - 1];
		}
		middle_c.a = middle_c.b;
		middle_c.b = b;
	}

	function middle_c_term(n) {
		var t = n;
		if (0 == t) { return 'S'; }
		if (1 == t) { return 'L'; }
		if (t > 1) { t -= 2; }
		else { ++t; t *= -1; }
		if (null == middle_c) { middle_c = {
			m: [],
			L: [],
			S: [],
			a: 0,
			b: 0
		}}
		while (middle_c.m.length < t) {
			expand_middle_c();
		}
		return middle_c.m[t];
	}

	function find_option(s) {
		var o = document.getElementById(s);
		if (null == o) {return};
		opts[s] = (true == o.checked);
	}

	function find_options() {
		opts = new Object();
		find_option('arc_a');
		find_option('arc_b');
		find_option('b_long');
		find_option('b_short');
		find_option('tile');
		find_option('bars');
		find_option('ints');
		find_option('vert_a');
		find_option('vert_b');
	}

</script>
</head>
<body onload="init()">
	<canvas id="canvas" class="canvas"  width="320" height="280"></canvas>
		<form action="javascript:" method="get">
			<input type="button" value="&lt" 
				onClick="view_scroll(-1, 0)"/>
			<input type="button" value="&gt" 
				onClick="view_scroll(1, 0)"/>
			<input type="button" value="/\" 
				onClick="view_scroll(0, -1)"/>
			<input type="button" value="\/" 
				onClick="view_scroll(0, 1)"/>
			<input type="button" value="+"
				onClick="view_zoom(1)"/>
			<input type="button" value="-"
				onClick="view_zoom(-1)"/>
			<span id="status"></span>
			<br />
			<input type="checkbox" id="arc_a" checked="checked" onClick="animate_tiles()" /> 
			<input type="checkbox" id="arc_b" checked="checked" onClick="animate_tiles()" /> 
			<input type="checkbox" id="b_long" checked="checked" onClick="animate_tiles()" /> 
			<input type="checkbox" id="b_short" checked="checked" onClick="animate_tiles()" /> 
			<input type="checkbox" id="tile" checked="checked" onClick="animate_tiles()" /> 
			<input type="checkbox" id="bars" checked="checked" onClick="animate_tiles()" /> 
			<input type="checkbox" id="ints" checked="checked" onClick="animate_tiles()" /> 
			<input type="checkbox" id="vert_a" checked="checked" onClick="animate_tiles()" /> 
			<input type="checkbox" id="vert_b" checked="checked" onClick="animate_tiles()" /> 
		</form>
		<!--<div id="message"></div>-->
	<div id="console"></div>
</body>
</html>
